為了更理解Alpine initramfs的眉角,今天持續來看一下Alpine的mkinitfs
套件裡面的 nlplug-findfs 這隻helper tool。
然而這邊有些先備知識需要說,在Linux的世界觀裡,為了因應動態插拔裝置的狀況,有整套相輔相成的框架/機制,分別是 Kobject
跟 uevent
。
簡單來說(我們不提Bus/Class/Device......等等深入的技術細節、以我們的需求只講概念就好),今天在kernel對應的init stage進行平台init、或著有裝置被動態放進系統時,driver在進行probe(),會建立出對應的kobject、掛上一顆Linux在開機時逐步打造出來的kobject樹狀結構之中。在裝置是被動態放進來時,就會發生像某個起床氣很重的巫妖王誕生時一樣,整個羅德隆森林都在呼喊他的名諱:阿薩 — — 咳咳錯棚,該裝置對應的kobject建立完會順便呼叫 kobject_uevent_env
,將這個裝置誕生的喜訊通知整個世界,包含了userspace的hotplug管理程式。
在過去、或現代在編譯kernel時如果有設定 UEVENT_HELPER_PATH
,亦或著打開 UEVENT_HELPER
爾後再透過 echo
把管理程式fullpath寫到/sys/kernel/uevent_helper
中,那麼kernel就會在uevent產生時,事敝躬親地去fork、exec該管理程式,並且把裝置名稱、路徑、各式大小資訊排在環境變數中、交給hotplug管理程式自己決定要怎麼建立 /dev/
底下的char/block dev;而且同時也會在sysfs底下建立一份「留存」。
話說到這裡,有個雞蛋問題,在kernel剛剛累死人地把第一支 process (initramfs時的/init
)拉起來時,裝置的kobject數早就建完了,可是/dev/底下還是空的、因為當時不會有人可以接uevent(事實上kernel init在建tree時也不會打),那麼、要怎麼建出 /dev
底下那票device file呢?
答案是— —手動戳 sysfs底下的裝置的 uevent
,然後他就會打了uevent上來給人接了。
長篇大論講這麼多,其實就是要帶出 nlplug-findfs
的目的,一如前幾偏一直強調的,Alpine的rootfs在一般預設下,是boot time去抓alpine_repo
指向的地方、透過套件管理系統一個一個裝套件裝出來的,如果是走網路、那就歸另外一條,但是如果今天Alpine Init需要的東西、或著alpine_repo指向block dev呢?以一些distro的設計哲學,可能會選擇一條道路,那就是直接用 busybox
的 mdev
直接下一道 mdev -s
,此時mdev
就會去遍歷 /sys
、一個一個把 uevent
戳起來,全部按照udev rules拉到 /dev
底下,再看要怎麼處理。
但是Alpine的設計理念是不做多餘的事情、也不要建立不必要的檔案,最小、最輕量化。Alpine Init呼叫的方式為: nlplug-findfs -p /sbin/mdev -d -n -a /tmp/apkovls
簡單意義是,開始去/sys
底下,有智慧性地找device node,然後戳它、叫他噴ADD
uevent上來,但要拿 /sbin/mdev
來幫我接 uevent (其實 nlplug-findfs
自己有一些handle_uevent的邏輯、不過最小effort嘛,有busybox那就叫它作就好)
static void trigger_uevent_cb(struct recurse_opts *opts, void *data)
{
size_t oldlen;
int fd;
if (!recurse_push(opts, &oldlen, "uevent"))
return;
fd = open(opts->path, O_WRONLY | O_CLOEXEC);
if (fd >= 0) {
write(fd, "add", 3);
close(fd);
}
recurse_pop(opts, oldlen);
}
在茫茫kobject
中這樣不斷的戳uevent起來ADDD,直到幫我找到某個有檔案系統的地方,含有apkovls.tar.gz
這個檔案
static void scandev_cb(struct recurse_opts *opts, void *data)
{
struct scandevctx *ctx = data;
struct ueventconf *conf = ctx->conf;
if (opts->is_dir) {
size_t oldlen;
int ok = 0;
if (recurse_push(opts, &oldlen, ".boot_repository")) {
ok = access(opts->path, F_OK) == 0;
recurse_pop(opts, oldlen);
}
if (ok) {
dbg("added boot repository %s to %s", opts->path, conf->bootrepos);
append_line(conf->bootrepos, opts->path);
ctx->found |= FOUND_BOOTREPO;
}
} else if (fnmatch("*.apkovl.tar.gz*", opts->filename, 0) == 0) {
dbg("found apkovl %s", opts->path);
append_line(conf->apkovls, opts->path);
ctx->found |= FOUND_APKOVL;
}
}
然後把他mount好、並且把檔案系統的path寫到 /tmp/apkovls
之中,而那個apkovl.tar.gz,會帶有repo套件庫、跟必要的檔案,以供後續讓Alpine Init拼出rootfs。
下一篇,我們應該會正式進入繼續手把手組出可以用的開機流程路上~